今天我們的主題是安全密碼產生工具,在目前想到的規劃中,
有些小工具如果使用,那我會覺得挺好用的,以密碼產生工具來說那會是值得選擇的作法
現今其實有很多方式產生安全性密碼的做法,像是我個人如果如果只想用簡單的,
那我個人會用 openssl rand -hex 16
或是 base64
去產生一段密碼使用,
不但如此,現在瀏覽器,或是 mac 都有內建可以產生密碼的好東西。
不過當然,能自己做一個再好不過了
說到密碼,肯定有一定的亂數層面
rand
: 我們用這個產生隨機文字
clap
: 我們用這個處理 cli 參數
cargo new password_generator
cd password_generator
cargo.toml
[dependencies]
rand = "0.8"
clap = { version = "4.0", features = ["derive"] }
use clap::Parser;
use rand::{thread_rng, Rng};
use std::collections::HashSet;
#[derive(Parser)]
#[command(name = "password_generator")]
#[command(about = "安全密碼產生器")]
pub struct Args {
/// 密碼長度
#[arg(short, long, default_value_t = 12)]
pub length: usize,
/// 包含大寫字母
#[arg(long, default_value_t = true)]
pub uppercase: bool,
/// 包含小寫字母
#[arg(long, default_value_t = true)]
pub lowercase: bool,
/// 包含數字
#[arg(long, default_value_t = true)]
pub numbers: bool,
/// 包含特殊符號
#[arg(long, default_value_t = false)]
pub symbols: bool,
/// 排除相似字符 (0, O, l, 1, etc.)
#[arg(long, default_value_t = false)]
pub exclude_ambiguous: bool,
/// 產生密碼數量
#[arg(short, long, default_value_t = 1)]
pub count: usize,
/// 每個字符集的最小數量
#[arg(long, default_value_t = 1)]
pub min_each: usize,
}
#[derive(Debug)]
pub struct PasswordConfig {
pub length: usize,
pub character_sets: Vec<&'static str>,
pub min_each: usize,
pub exclude_ambiguous: bool,
}
pub struct PasswordGenerator {
config: PasswordConfig,
}
impl PasswordGenerator {
pub fn new(config: PasswordConfig) -> Result<Self, String> {
if config.length < 4 {
return Err("密碼長度至少需要 4 個字符".to_string());
}
if config.character_sets.is_empty() {
return Err("至少需要選擇一種字符集".to_string());
}
let min_required = config.character_sets.len() * config.min_each;
if config.length < min_required {
return Err(format!(
"密碼長度 {} 不足以滿足每種字符集最少 {} 個字符的要求",
config.length, config.min_each
));
}
Ok(Self { config })
}
pub fn generate(&self) -> String {
let mut rng = thread_rng();
let mut password = Vec::new();
// 確保每種字符集都有最少數量的字符
for charset in &self.config.character_sets {
let filtered_charset = if self.config.exclude_ambiguous {
self.filter_ambiguous_chars(charset)
} else {
charset.to_string()
};
for _ in 0..self.config.min_each {
let chars: Vec<char> = filtered_charset.chars().collect();
let random_char = chars[rng.gen_range(0..chars.len())];
password.push(random_char);
}
}
// 填充剩餘長度
let all_chars = self.get_all_characters();
let remaining = self.config.length - password.len();
for _ in 0..remaining {
let chars: Vec<char> = all_chars.chars().collect();
let random_char = chars[rng.gen_range(0..chars.len())];
password.push(random_char);
}
// 隨機打亂密碼
for i in 0..password.len() {
let j = rng.gen_range(0..password.len());
password.swap(i, j);
}
password.into_iter().collect()
}
fn get_all_characters(&self) -> String {
let mut all_chars = String::new();
for charset in &self.config.character_sets {
if self.config.exclude_ambiguous {
all_chars.push_str(&self.filter_ambiguous_chars(charset));
} else {
all_chars.push_str(charset);
}
}
all_chars
}
fn filter_ambiguous_chars(&self, charset: &str) -> String {
let ambiguous_chars: HashSet<char> =
"0O1lI|`".chars().collect();
charset.chars()
.filter(|c| !ambiguous_chars.contains(c))
.collect()
}
}
pub const UPPERCASE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
pub const LOWERCASE: &str = "abcdefghijklmnopqrstuvwxyz";
pub const NUMBERS: &str = "0123456789";
pub const SYMBOLS: &str = "!@#$%^&*()_+-=[]{}|;:,.<>?";
pub fn build_config_from_args(args: &Args) -> Result<PasswordConfig, String> {
let mut character_sets = Vec::new();
if args.uppercase {
character_sets.push(UPPERCASE);
}
if args.lowercase {
character_sets.push(LOWERCASE);
}
if args.numbers {
character_sets.push(NUMBERS);
}
if args.symbols {
character_sets.push(SYMBOLS);
}
PasswordConfig {
length: args.length,
character_sets,
min_each: args.min_each,
exclude_ambiguous: args.exclude_ambiguous,
}
}
pub fn evaluate_password_strength(password: &str) -> (&'static str, f32) {
let mut score = 0f32;
let length = password.len() as f32;
// 長度評分
score += (length / 4.0).min(6.0);
// 字符多樣性評分
let has_lower = password.chars().any(|c| c.is_ascii_lowercase());
let has_upper = password.chars().any(|c| c.is_ascii_uppercase());
let has_digit = password.chars().any(|c| c.is_ascii_digit());
let has_symbol = password.chars().any(|c| !c.is_alphanumeric());
let variety = [has_lower, has_upper, has_digit, has_symbol]
.iter().filter(|&&x| x).count() as f32;
score += variety * 2.0;
// 熵評估
let unique_chars = password.chars().collect::<HashSet<_>>().len() as f32;
score += (unique_chars / length * 4.0).min(4.0);
let strength = match score as i32 {
0..=6 => ("弱", score / 16.0),
7..=10 => ("中等", score / 16.0),
11..=14 => ("強", score / 16.0),
_ => ("非常強", score / 16.0),
};
strength
}
use clap::Parser;
mod password_generator;
use password_generator::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
// 驗證參數
if args.length == 0 {
eprintln!("錯誤: 密碼長度必須大於 0");
std::process::exit(1);
}
if args.count == 0 {
eprintln!("錯誤: 密碼數量必須大於 0");
std::process::exit(1);
}
// 建立密碼配置
let config = match build_config_from_args(&args) {
Ok(config) => config,
Err(e) => {
eprintln!("配置錯誤: {}", e);
std::process::exit(1);
}
};
// 建立密碼產生器
let generator = match PasswordGenerator::new(config) {
Ok(gen) => gen,
Err(e) => {
eprintln!("產生器初始化失敗: {}", e);
std::process::exit(1);
}
};
// 產生密碼
println!("=== 安全密碼產生器 ===");
println!("配置: 長度={}, 數量={}", args.length, args.count);
println!("字符集: {}{}{}{}",
if args.uppercase { "大寫 " } else { "" },
if args.lowercase { "小寫 " } else { "" },
if args.numbers { "數字 " } else { "" },
if args.symbols { "符號 " } else { "" }
);
if args.exclude_ambiguous {
println!("已排除相似字符");
}
println!("\n產生的密碼:");
println!("{:-<50}", "");
for i in 1..=args.count {
let password = generator.generate();
let (strength, score) = evaluate_password_strength(&password);
println!("密碼 {:2}: {}", i, password);
println!(" 強度: {} ({:.1}%)", strength, score * 100.0);
if i < args.count {
println!();
}
}
println!("{:-<50}", "");
println!("提醒: 你的密碼已經完成了好誒~~");
Ok(())
}
# 產生預設 12 位密碼
cargo run
# 產生 16 位包含符號的密碼
cargo run -- --length 16 --symbols
# 產生 5 個 20 位的複雜密碼
cargo run -- --length 20 --symbols --count 5
# 產生不包含相似字符的密碼
cargo run -- --length 15 --symbols --exclude-ambiguous
# 只使用數字和小寫字母
cargo run -- --length 10 --no-uppercase --symbols
Day 5 :: 打完收工,不仿自己玩玩看~~